#region Using Statements

using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Xfinity.Console;
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;


#endregion

namespace Xfinity.Engine
{
    /// <summary>
    /// A callback function for the event of a keypress.
    /// </summary>
    [Serializable]
    public delegate void KeyEventHandler();
    /// <summary>
    /// A callback function for the event of mouse movement.
    /// </summary>
    [Serializable]
    public delegate void MouseEventHandler();

    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public partial class InputHandler : GameComponent
    {
        /// <summary>
        /// Constructs a new instance of the keyboard listener.
        /// </summary>
        /// <param name="game">The Game object.</param>
        public InputHandler(Game game)
            : base(game)
        {
            game.Services.AddService(typeof(InputHandler), this);
            game.Components.Add(this);
            UpdateOrder = int.MinValue;
        }


        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            GraphicsDevice gd = ((IGraphicsDeviceService)(Game.Services.GetService(typeof(IGraphicsDeviceService)))).GraphicsDevice;
            halfWidth = gd.Viewport.Width / 2;
            halfHeight = gd.Viewport.Height / 2;
            InitialiseHighDefinitionMouseInput();
            base.Initialize();
        }
        private static void InitialiseHighDefinitionMouseInput()
        {
            //System.Windows.Forms.Form form = (System.Windows.Forms.Form)System.Windows.Forms.Form.FromHandle(GameManager.Game.Window.Handle);
            //System.Windows.Forms.Application.AddMessageFilter(new WM_INPUTMessageFilter());
        }
        /// <summary>
        /// A message filter.
        /// </summary>
        public class WM_INPUTMessageFilter : System.Windows.Forms.IMessageFilter
        {

            #region IMessageFilter Members
            /// <summary>
            /// Filters a message.
            /// </summary>
            /// <param name="m">The message.</param>
            /// <returns>Whether to cull the message.</returns>
            public bool PreFilterMessage(ref System.Windows.Forms.Message m)
            {
                if (m.Msg == 0xff)
                {

                }
                return false;
            }

            #endregion
        }
        #region Static properties
        private static KeyboardState oldKeyboardState;
        private static KeyboardState keyboardState;
        /// <summary>
        /// The keyboard state.
        /// </summary>
        public static KeyboardState KeyboardState
        {
            [DebuggerStepThrough]
            get { return keyboardState; }
        }
        /// <summary>
        /// The old keyboard state.
        /// </summary>
        public static KeyboardState OldKeyboardState
        {
            [DebuggerStepThrough]
            get { return oldKeyboardState; }
        }
        private static MouseState oldMouseState = new MouseState();
        /// <summary>
        /// The old mouse state.
        /// </summary>
        public static MouseState OldMouseState
        {
            [DebuggerStepThrough]
            get { return oldMouseState; }
        }
        private static Vector2 mousePos;
        /// <summary>
        /// The mouse position.
        /// </summary>
        public static Vector2 MousePosition
        {
            [DebuggerStepThrough]
            get { return mousePos; }
        }
        private static MouseState mouseState = new MouseState();
        /// <summary>
        /// The mouse state.
        /// </summary>
        public static MouseState MouseState
        {
            [DebuggerStepThrough]
            get { return mouseState; }
        }
        private static int deltaScroll;

        /// <summary>
        /// The mouse scroll moved since last update.
        /// </summary>
        public static int DeltaScroll
        {
            get { return deltaScroll; }
            set { deltaScroll = value; }
        }

        private static double relativeMousePositionX;
        /// <summary>
        /// The relative mouse x position.
        /// </summary>
        public static double RelativeMousePositionX
        {
            [DebuggerStepThrough]
            get { return relativeMousePositionX; }
        }
        private static double relativeMousePositionY;
        /// <summary>
        /// The relative mouse y position.
        /// </summary>
        public static double RelativeMousePositionY
        {
            [DebuggerStepThrough]
            get { return relativeMousePositionY; }
        }


        private static int halfWidth, halfHeight;
        #endregion
        #region Update
        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (!Game.IsActive)
            {
                return;
            }

            UpdateState();
            int newScroll = StandardConsole.BottomViewLineOffset + DeltaScroll;
            StandardConsole.BottomViewLineOffset = (int)MathHelper.Clamp(newScroll, 0, StandardConsole.Transcript.Count);
            if (!StandardConsole.ConsoleShowing)
            {
                HandleMouse();
                HandleKeyboard(gameTime);
            }
            else
            {
                #region Writing at the Command line.
                // writing at the Command line; so this doesn't need to be optimised

                StandardConsole.BottomViewLineOffset += InputHandler.DeltaScroll;

                if (IsKeyJustPressed(Keys.OemTilde))
                {
                    StandardConsole.ToggleConsole(null);
                    return;
                }
                else if (IsKeyJustPressed(Keys.Enter))
                {
                    string line = StandardConsole.InputLine.ToString();
                    UpdateConsoleTranscript(line);
                    ExecuteCommand(line);

                }
                else if (IsKeyJustPressed(Keys.Back))
                {
                    StandardConsole.ResetTabCompletion();
                    if (StandardConsole.InputLine.Length >= 1)
                    {
                        StandardConsole.InputLine.Remove(StandardConsole.InputLine.Length - 1, 1);
                    }
                }
                else if (IsKeyJustPressed(Keys.OemSemicolon))
                {
                    if (IsShiftPressed())
                    {
                        StandardConsole.InputLine.Append(':');
                    }
                    else
                    {
                        StandardConsole.InputLine.Append(';');
                    }
                }
                else if (IsKeyJustPressed(Keys.OemPipe))
                {
                    if (IsShiftPressed())
                    {
                        StandardConsole.InputLine.Append('|');
                    }
                    else
                    {
                        StandardConsole.InputLine.Append('\\');

                    }
                }
                else if (IsKeyJustPressed(Keys.Space))
                {
                    StandardConsole.InputLine.Append(' ');
                    StandardConsole.ResetTabCompletion();
                }
                else if (IsKeyJustPressed(Keys.OemPeriod))
                {
                    if (IsShiftPressed())
                    {
                        StandardConsole.InputLine.Append('>');
                    }
                    else
                    {
                        StandardConsole.InputLine.Append('.');

                    }
                }
                else if (IsKeyJustPressed(Keys.Tab))
                {

                    if (!String.IsNullOrEmpty(StandardConsole.TabCompletedLine))
                    {
                        StandardConsole.InputLine.Remove(0, StandardConsole.InputLine.Length);
                        StandardConsole.InputLine.Append(StandardConsole.CurrentTabMatches[StandardConsole.TabCycle]);
                        StandardConsole.ResetTabCompletion();
                    }
                    else
                    {
                        StandardConsole.ShowNextTabCompletion();
                    }
                }
                else if (IsKeyJustPressed(Keys.Up))
                {
                    if (String.IsNullOrEmpty(StandardConsole.TabCompletedLine))
                    {
                        StandardConsole.InputHistoryPosition++;
                        StandardConsole.ChangeInputToHistory(StandardConsole.InputHistoryPosition);
                    }
                    else
                    {
                        StandardConsole.DecreaseTabCompletion();
                    }

                }
                else if (IsKeyJustPressed(Keys.Down))
                {
                    if (String.IsNullOrEmpty(StandardConsole.TabCompletedLine))
                    {
                        StandardConsole.InputHistoryPosition--;
                        if (StandardConsole.InputHistoryPosition == -1)
                        {
                            StandardConsole.InputLine.Remove(0, StandardConsole.InputLine.Length);
                            StandardConsole.ResetTabCompletion();
                        }
                        else
                        {
                            StandardConsole.ChangeInputToHistory(StandardConsole.InputHistoryPosition);
                        }
                    }
                    else
                    {
                        StandardConsole.AdvanceTabCompletion();
                    }

                }
                else if (IsKeyJustPressed(Keys.Escape) && !String.IsNullOrEmpty(StandardConsole.TabCompletedLine))
                {
                    StandardConsole.ResetTabCompletion();
                }
                else
                {
                    bool input = false;
                    foreach (Keys key in keyboardState.GetPressedKeys())
                    {
                        if (oldKeyboardState.IsKeyDown(key) || key == Keys.LeftShift || key == Keys.RightShift)
                        {
                            continue;
                        }
                        string s = "";
                        bool number;
                        switch (key)
                        {
                            #region switching keys
                            case Keys.D0:
                            case Keys.D1:
                            case Keys.D2:
                            case Keys.D3:
                            case Keys.D4:
                            case Keys.D5:
                            case Keys.D6:
                            case Keys.D7:
                            case Keys.D8:
                            case Keys.D9:
                                {
                                    if (IsShiftPressed())
                                    {
                                        if (key == Keys.D0)
                                        {
                                            s = ")";
                                        }
                                        else if (key == Keys.D1)
                                        {
                                            s = "!";
                                        }
                                        else if (key == Keys.D2)
                                        {
                                            s = "@";
                                        }
                                        else if (key == Keys.D3)
                                        {
                                            s = "#";
                                        }
                                        else if (key == Keys.D4)
                                        {
                                            s = "$";
                                        }
                                        else if (key == Keys.D5)
                                        {
                                            s = "%";
                                        }
                                        else if (key == Keys.D6)
                                        {
                                            s = "^";
                                        }
                                        else if (key == Keys.D7)
                                        {
                                            s = "&";
                                        }
                                        else if (key == Keys.D8)
                                        {
                                            s = "*";
                                        }
                                        else if (key == Keys.D9)
                                        {
                                            s = "(";
                                        }
                                        number = false;
                                        break;
                                    }
                                    else
                                    {
                                        s = key.ToString().Substring(1);
                                        number = true;
                                        break;
                                    }
                                }
                            default:
                                {
                                    s = key.ToString();
                                    number = false;
                                    break;
                                }
                            #endregion
                        }
                        if (s.Length == 1 || number)
                        {
                            input = true;
                            AddLetterToInputLine(s);
                        }
                    }
                    if (input)
                    {
                        if (StandardConsole.TabCompletion)
                        {
                            StandardConsole.ShowNextTabCompletion();
                        }
                    }
                }
                #endregion
            }

        }

        private static void AddLetterToInputLine(string s)
        {
            if (keyboardState.IsKeyDown(Keys.LeftShift) || keyboardState.IsKeyDown(Keys.RightShift))
            {
                StandardConsole.InputLine.Append(s.ToUpper());
            }
            else
            {
                StandardConsole.InputLine.Append(s.ToLower());
            }
        }

        private static void ExecuteCommand(string line)
        {
            try
            {
                CommandParser.ProcessCommand(line);
            }
            catch (InvalidCommandException e)
            {
                StandardConsole.WriteLine(e.Commands[0].ToString() + ": Invalid Command.");
            }
        }

        private static void UpdateConsoleTranscript(string line)
        {
            StandardConsole.ResetTabCompletion();
            StandardConsole.Transcript.Add(line);
            StandardConsole.InputHistory.Add(line);
            StandardConsole.InputLine.Remove(0, StandardConsole.InputLine.Length);
        }

        private static void UpdateState()
        {
            // get states
            oldKeyboardState = keyboardState;
            keyboardState = Keyboard.GetState();
            oldMouseState = mouseState;
            mouseState = Mouse.GetState();

            DeltaScroll = MouseState.ScrollWheelValue - oldMouseState.ScrollWheelValue;
        }

        private static void HandleKeyboard(GameTime gameTime)
        {
            // loop through each of the 'key down' keys being watched
            foreach (KeyValuePair<Keys, KeyEventHandler> action in keysDown)
            {
                // test if the key has just been pressed
                if (keyboardState.IsKeyDown(action.Key) && (oldKeyboardState.IsKeyUp(action.Key)))
                {
                    System.Diagnostics.Debug.WriteLine(action.Key + " down.", gameTime.TotalGameTime.ToString());
                    // run the KeyEvent
                    action.Value();
                }
            }

            // loop through each of the 'key press' keys being watched
            foreach (KeyValuePair<Keys, KeyEventHandler> action in keysPress)
            {
                // test if the key has just been pressed
                if (keyboardState.IsKeyDown(action.Key) && (oldKeyboardState.IsKeyDown(action.Key)))

                    // run the KeyEvent
                    action.Value();
            }

            // loop through each of the 'key up' keys being watched
            foreach (KeyValuePair<Keys, KeyEventHandler> action in keysUp)
            {
                // check the key to see if it has just been released
                if ((oldKeyboardState.IsKeyDown(action.Key) && keyboardState.IsKeyUp(action.Key)))
                {
                    System.Diagnostics.Debug.WriteLine(action.Key + " up.", gameTime.TotalGameTime.ToString());

                    // run the event handler
                    action.Value();
                }
            }
        }

        private static void HandleMouse()
        {
            // test the mouse
            if (mouseState.X != halfWidth)
            {
                relativeMousePositionX = mouseState.X / (double)GraphicsHelper.GraphicsDevice.Viewport.Width;
                relativeMousePositionX *= 2;
                relativeMousePositionX -= 1;
                mousePos.X = (float)relativeMousePositionX;
                mouseMovedX();
            }
            else
            {
                relativeMousePositionX = 0;
                mousePos.X = 0;
                mouseXCenter();
            }
            if (mouseState.Y != halfHeight)
            {
                relativeMousePositionY = mouseState.Y / (double)GraphicsHelper.GraphicsDevice.Viewport.Height;
                relativeMousePositionY *= 2;
                relativeMousePositionY -= 1;
                mousePos.Y = (float)relativeMousePositionY;
                mouseMovedY();
            }
            else
            {
                relativeMousePositionY = 0;
                mousePos.Y = 0;
                mouseYCenter();
            }

            if (mousePos != Vector2.Zero)
            {
                Mouse.SetPosition(halfWidth, halfHeight);
            }
        }

        public static bool IsShiftPressed()
        {
            return keyboardState.IsKeyDown(Keys.LeftShift) || keyboardState.IsKeyDown(Keys.RightShift);
        }

        public static bool IsKeyJustPressed(Keys key)
        {
            return keyboardState.IsKeyDown(key) && !oldKeyboardState.IsKeyDown(key);
        }
        #endregion
        #region Keyboard input stuff
        /// <summary>
        /// Registers a key with both up and down events.
        /// </summary>
        /// <param name="key">The key to watch.</param>
        /// <param name="keyDown">The action to take when the key is pressed.</param>
        /// <param name="keyUp">The action to take when the key is released.</param>
        [DebuggerStepThrough]
        public static void RegisterKey(Keys key, KeyEventHandler keyDown, KeyEventHandler keyUp)
        {
            RegisterKeyDown(key, keyDown);
            RegisterKeyUp(key, keyUp);
        }
        /// <summary>
        /// Registers a key with both up and down events, and for being held.
        /// </summary>
        /// <param name="key">The key to watch.</param>
        /// <param name="keyDown">The action to take when the key is pressed.</param>
        /// /// <param name="keyPress">The action to take when the key is held down.</param>
        /// <param name="keyUp">The action to take when the key is released.</param>
        [DebuggerStepThrough]
        public static void RegisterKey(Keys key, KeyEventHandler keyDown, KeyEventHandler keyPress, KeyEventHandler keyUp)
        {
            RegisterKeyDown(key, keyDown);
            RegisterKeyPress(key, keyPress);
            RegisterKeyUp(key, keyUp);
        }
        /// <summary>
        /// Register an action to be taken when the specified key is held down.
        /// </summary>
        /// <param name="key">The key to watch.</param>
        /// <param name="handler">The action to take.</param>
        [DebuggerStepThrough]
        private static void RegisterKeyPress(Keys key, KeyEventHandler handler)
        {
            if (keysPress.ContainsKey(key))
            {
                keysPress[key] += handler;
                System.Diagnostics.Debug.WriteLine("Added a new listener for " + key, "Key Press registered");
            }
            else
            {
                keysPress.Add(key, handler);
                System.Diagnostics.Debug.WriteLine("Created listener for " + key, "Key Press registered");
            }
        }

        /// <summary>
        /// Binds a key to a Command.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="command">The Command.</param>
        public static void BindToCommand(Keys key, string command)
        {
            CommandStruct c = new CommandStruct();
            c.Command = command;
            boundCommands.Add(new KeyMapping(key, command));
            RegisterKeyDown(key, c.Execute);
        }
        /// <summary>
        /// A key mapping.
        /// </summary>
        public struct KeyMapping
        {
            private Keys key;

            /// <summary>
            /// The bound key.
            /// </summary>
            [XmlAttribute]
            public Keys Key
            {
                get { return key; }
                set { key = value; }
            }
            private string command;

            /// <summary>
            /// The Command.
            /// </summary>
            [XmlAttribute]
            public string Command
            {
                get { return command; }
                set { command = value; }
            }
            /// <summary>
            /// Constructs a new key mapping.
            /// </summary>
            /// <param name="key">The key.</param>
            /// <param name="Command">The Command.</param>
            public KeyMapping(Keys key, string command)
            {
                this.key = key;
                this.command = command;
            }
        }
        private static List<KeyMapping> boundCommands = new List<KeyMapping>();

        /// <summary>
        /// The bound commands.
        /// </summary>
        public static List<KeyMapping> BoundCommands
        {
            get { return boundCommands; }
            set { boundCommands = value; }
        }
        /// <summary>
        /// A delegate Command.
        /// </summary>
        [Serializable]
        struct CommandStruct
        {
            public string Command;
            public void Execute()
            {
                CommandParser.ProcessCommand(Command);
            }
        }
        /// <summary>
        /// Register an action to be taken when the specified key is pressed.
        /// </summary>
        /// <param name="key">The key to watch.</param>
        /// <param name="handler">The action to take.</param>
        [DebuggerStepThrough]
        public static void RegisterKeyDown(Keys key, KeyEventHandler handler)
        {
            if (keysDown.ContainsKey(key))
            {
                keysDown[key] += handler;
                System.Diagnostics.Debug.WriteLine("Added a new listener for " + key, "Key Down registered");
            }
            else
            {
                keysDown.Add(key, handler);
                System.Diagnostics.Debug.WriteLine("Created listener for " + key, "Key Down registered");
            }
        }
        #region Register mouse events
        /// <summary>
        /// Registers a mouse movement handler.
        /// </summary>
        /// <param name="handler">The handler.</param>
        [DebuggerStepThrough]
        public static void RegisterMouseMovedX(MouseEventHandler handler)
        {
            mouseMovedX += handler;
        }
        /// <summary>
        /// Registers a mouse movement handler.
        /// </summary>
        /// <param name="handler">The handler.</param>
        [DebuggerStepThrough]
        public static void RegisterMouseMovedY(MouseEventHandler handler)
        {
            mouseMovedY += handler;
        }
        /// <summary>
        /// Registers a mouse movement handler.
        /// </summary>
        /// <param name="handler">The handler.</param>
        [DebuggerStepThrough]
        public static void RegisterMouseMovement(MouseEventHandler handler)
        {
            mouseMovedX += handler;
            mouseMovedY += handler;
        }
        /// <summary>
        /// Registers a mouse movement handler.
        /// </summary>
        /// <param name="handler">The handler.</param>
        [DebuggerStepThrough]
        public static void RegisterMouseXCentered(MouseEventHandler handler)
        {
            mouseXCenter += handler;
        }
        /// <summary>
        /// Registers a mouse movement handler.
        /// </summary>
        /// <param name="handler">The handler.</param>
        [DebuggerStepThrough]
        public static void RegisterMouseYCentered(MouseEventHandler handler)
        {
            mouseYCenter += handler;
        }
        /// <summary>
        /// Registers a mouse movement handler.
        /// </summary>
        /// <param name="handler">The handler.</param>
        [DebuggerStepThrough]
        public static void RegisterMouseCentered(MouseEventHandler handler)
        {
            mouseXCenter += handler;
            mouseYCenter += handler;
        }
        #endregion
        /// <summary>
        /// Register an action to be taken when the specified key is released.
        /// </summary>
        /// <param name="key">The key to watch.</param>
        /// <param name="handler">The action to take.</param>
        [DebuggerStepThrough]
        public static void RegisterKeyUp(Keys key, KeyEventHandler handler)
        {
            if (keysUp.ContainsKey(key))
            {
                keysUp[key] += handler;
                System.Diagnostics.Debug.WriteLine("Added a new listener for " + key, "Key Up registered");
            }
            else
            {
                keysUp.Add(key, handler);
                System.Diagnostics.Debug.WriteLine("Created listener for " + key, "Key Up registered");
            }
        }
        /// <summary>
        /// Saves the bound mappings.
        /// </summary>
        /// <param name="filename">The filename.</param>
        public static void SaveMappings(string filename)
        {
            try
            {
                using (FileStream s = File.Open(filename, FileMode.Create, FileAccess.Write))
                {
                    SaveMappings(s);
                }                
            }
            catch (IOException)
            {
                StandardConsole.Fail("Error: Mappings file already in use.");
            }

        }
        /// <summary>
        /// Saves key mappings.
        /// </summary>
        /// <param name="commands">Commands.</param>
        [BindCommand("SaveKeyMappings", DefaultBoundKey = Keys.F1)]
        public static void SaveMappingsCommand(string[] commands)
        {
            SaveMappings(@".\keys.xml");
        }
        /// <summary>
        /// Saves mappings to a stream.
        /// </summary>
        /// <param name="stream"></param>
        public static void SaveMappings(Stream stream)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<KeyMapping>));

            serializer.Serialize(stream, boundCommands);

        }
        #endregion
        #region Events and stuff.
        private static Dictionary<Keys, KeyEventHandler> keysDown = new Dictionary<Keys, KeyEventHandler>();
        private static Dictionary<Keys, KeyEventHandler> keysPress = new Dictionary<Keys, KeyEventHandler>();
        private static Dictionary<Keys, KeyEventHandler> keysUp = new Dictionary<Keys, KeyEventHandler>();
        private static MouseEventHandler mouseMovedX = delegate { };
        private static MouseEventHandler mouseMovedY = delegate { };
        private static MouseEventHandler mouseXCenter = delegate { };
        private static MouseEventHandler mouseYCenter = delegate { };
        #endregion
    }

}